package hydrap2p.library;

import hydrap2p.helper.Utility;
import hydrap2p.logging.Level;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;

/**
 * Manages what files are open, and any file access to them
 * @author Hufman
 *
 */
public class FileAccess {

	private Map<Long,Boolean> lockedfiles;
	private static final int MAXOPEN=20000;
	
	public FileAccess()
	{
		lockedfiles=new HashMap<Long,Boolean>((int)(MAXOPEN*1.5));
	}

	private static void log(Level level, String source, String reason)
	{
		LibraryLogging.log(level, source, reason);
	}
	private boolean lock(Long uid)
	{
		boolean ret=false;
		while (true)
		{
			try {
				synchronized(lockedfiles){
					if (lockedfiles.containsKey(uid))
					{
						lockedfiles.wait(3000);
					}
					if (!lockedfiles.containsKey(uid))
					{
						ret=true;
						lockedfiles.put(uid, true);
						
						if (!lockedfiles.containsKey(uid)) {
							System.out.println("!O Failed to save lock!;");
						}
						break;
					}
					/*
					else
						log(Level.INFO,"Library - File Manager","Lock contention while locking file for download "+uid);
					*/
				}
			} catch (InterruptedException e) {
				
			}
		}
		return ret;
	}
	private void unlock(Long uid)
	{
		if (lockedfiles.containsKey(uid))
		{
			synchronized(lockedfiles)
			{
				if (lockedfiles.containsKey(uid))
				{
					lockedfiles.remove(uid);
					lockedfiles.notifyAll();
				}
			}
		}
	}
	
	/**
	 * Given a full path to a file, attempt to create it
	 * If it already exists, throw an exception
	 * Automatically create parent paths
	 * @param pathfile A full absolute path
	 * @return
	 * @throws IOException 
	 * @throws FileAlreadyExists 
	 * @throws IOProblem 
	 */
	public File create(String pathfile) throws IOException, FileAlreadyExists, IOProblem
	{
		boolean success=false;
		File ret=new File(pathfile);
		if (ret.exists())
			throw new FileAlreadyExists(pathfile);
		if (!ret.getParentFile().isDirectory())
		{
			success=ret.getParentFile().mkdirs();
			if (!success)
				throw new IOProblem(pathfile,"Could not create parent directories");
		}
		success=ret.createNewFile();
		if (!success)
			throw new FileAlreadyExists(pathfile);
		return ret;
	}
	
	public void saveData(ItemInfo file, long offset, byte[] data, int dataOffset, int dataLen) throws FileNotFoundException, IOException
	{
		try {
			lock(file.uid);
			File accessing=new File(Utility.appendPath(file.pathname, file.name));
			RandomAccessFile random=new RandomAccessFile(accessing,"rw");
			random.seek(offset);
			random.write(data, dataOffset, dataLen);
			random.close();
		}
		finally {
			unlock(file.uid);
		}
	}
	
	public byte[] loadData(ItemInfo file, long offset, int len) throws FileNotFoundException, IOException
	{
		try {
			lock(file.uid);
			byte[] ret=new byte[len];
			File accessing=new File(Utility.appendPath(file.pathname, file.name));
			RandomAccessFile random=new RandomAccessFile(accessing,"r");
			random.seek(offset);
			
			int read=1;
			int totalread=0;
			while (read>0 && totalread<len){
				read=random.read(ret, totalread, len-totalread);
				if (read>0)
				totalread+=read;
			}
			random.close();
			return ret;
		}
		finally {
			unlock(file.uid);
		}
	}
	public boolean delete(ItemInfo file)
	{
		try {
			lock(file.uid);
			File accessing=new File(Utility.appendPath(file.pathname, file.name));
			boolean ret=accessing.exists();
			boolean ret2=accessing.delete();
			return ret && ret2;
		}
		finally {
			unlock(file.uid);
		}
	}
	
	/**
	 * Moves the file, but does not update ItemInfo
	 * @param file
	 * @param newpath
	 * @param newname
	 * @throws IOProblem 
	 */
	public void move(ItemInfo file, String newpath, String newname) throws FileAlreadyExists, IOException, IOProblem
	{
		boolean success=false;
		try {
			lock(file.uid);
			File accessing=new File(Utility.appendPath(file.pathname, file.name));
			File other=new File(Utility.appendPath(newpath,newname));
			if (other.exists())
				throw new FileAlreadyExists(other.getAbsolutePath());
			
			if (!other.getParentFile().isDirectory())
			{
				success=other.getParentFile().mkdirs();
				if (!success)
					throw new IOProblem(other.getAbsolutePath(),"Failed to create parent directories before moving");
			}
			success=accessing.renameTo(other);
			if (!success)
			{
				// Could not nicely move, so hand-move 
				InputStream reader=null;
				OutputStream writer=null;
				try {
					success=other.createNewFile();
					if (!success)
						throw new FileAlreadyExists(other.getAbsolutePath());
					reader=new BufferedInputStream(new FileInputStream(accessing));
					writer=new BufferedOutputStream(new FileOutputStream(other));
					
					int read=0;
					byte[] temp=new byte[1024];
					while (read>=0)
					{
						read=reader.read(temp);
						if (read>=0)
							writer.write(temp, 0, read);
					}
				}
				catch (IOException e)
				{
					e.printStackTrace();
					if (other.exists())
					{
						success=other.delete();
						if (!success)
							throw new IOProblem(other.getAbsolutePath(),"Could not delete new file after failing to move");
					}
					throw e;
				}
				finally
				{
					if (reader!=null)
						reader.close();
					if (writer!=null)
						writer.close();
				}
				success=accessing.delete();
				if (!success)
				{
					success=other.delete();
					if (success)
						throw new IOProblem(accessing.getAbsolutePath(),"Could not delete old file after moving, deleting new file");
					else
						throw new IOProblem(accessing.getAbsolutePath(),"Could not delete old file after moving, and could not delete new file, so there is now a duplicate");
					
				}
			}
		}
		finally {
			unlock(file.uid);
		}
	}
}
